﻿////////////////////////////////////////////////////////////////////////////////////////////////
/// Home Automation, Parallel Port Interfacing
///
/// Researched, Developed and Tested by Michael Angelo Yap
///
/// (C)opyright 2012. All Rights Reserved.
/// http://www-michael-yap.com
/// 
/// Additional References for INPUT:
/// http://www.bowdenshobbycircuits.info/page6.htm
/// http://www.codeproject.com/Articles/15020/Reading-from-Parallel-Port-using-Inpout32-dll
////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Plexer.ParallelPort
{
    //Decimal - Strobe(pin1,black) - Linefeed (pin14,white)

    // 1 - 10 Inverted 01 -> call this to exec Linefeed
    // 2 - 01 Inverted 10 -> call this to exec Strobe
    // 3 - 00 Turn Off, always call this
    // 4 - 11 Turn On, dunno why should I use this one

    public class Manage
    {
        private static bool isParallelPortInUse = false;
        public static EventHandler<InputPortPinStatusArgs> OnInputStatusTrigger;

        #region 32bit
        [DllImport("inpout32.dll")]
        private static extern UInt32 IsInpOutDriverOpen();

        [DllImport("inpout32.dll")]
        private static extern void Output32(short PortAddress, short Data);

        [DllImport("inpout32.dll")]
        private static extern char Input32(short PortAddress);

        [DllImport("inpout32.dll")]
        private static extern void DlPortWritePortUshort(short PortAddress, ushort Data);

        [DllImport("inpout32.dll")]
        private static extern ushort DlPortReadPortUshort(short PortAddress);

        [DllImport("inpout32.dll")]
        private static extern void DlPortWritePortUlong(int PortAddress, uint Data);

        [DllImport("inpout32.dll")]
        private static extern uint DlPortReadPortUlong(int PortAddress);
        #endregion

        #region 64bit calls
        [DllImport("inpoutx64.dll", EntryPoint = "IsInpOutDriverOpen")]
        private static extern UInt32 IsInpOutDriverOpen_x64();

        [DllImport("inpoutx64.dll", EntryPoint = "Out32")]
        static extern void Output64(short PortAddress, short Data);

        [DllImport("inpoutx64.dll", EntryPoint = "Inp32")]
        private static extern char Input64(short PortAddress);

        [DllImport("inpoutx64.dll", EntryPoint = "DlPortWritePortUshort")]
        private static extern void DlPortWritePortUshort_x64(short PortAddress, ushort Data);

        [DllImport("inpoutx64.dll", EntryPoint = "DlPortReadPortUshort")]
        private static extern ushort DlPortReadPortUshort_x64(short PortAddress);

        [DllImport("inpoutx64.dll", EntryPoint = "DlPortWritePortUlong")]
        private static extern void DlPortWritePortUlong_x64(int PortAddress, uint Data);

        [DllImport("inpoutx64.dll", EntryPoint = "DlPortReadPortUlong")]
        private static extern uint DlPortReadPortUlong_x64(int PortAddress);

        [DllImport("inpoutx64.dll", EntryPoint = "GetPhysLong")]
        private static extern bool GetPhysLong_x64(ref int PortAddress, ref uint Data);

        [DllImport("inpoutx64.dll", EntryPoint = "SetPhysLong")]
        private static extern bool SetPhysLong_x64(ref int PortAddress, ref uint Data);
        #endregion
        
        public static ParallelPortInfo parallelportinfo = new ParallelPortInfo();

        public static void TurnAllOff()
        {
            Output(parallelportinfo.DataPortAddress, (int)SwitchingState.Off);
            parallelportinfo.Pins.Clear();
            for (Int16 x = 0; x < (parallelportinfo.MC74HC95_Total * 8); x++)
            {
                ExecuteStrobe();
                parallelportinfo.Pins.Add(new PinInfo
                {
                    MC74HC95_index = Convert.ToInt16(x / 8),
                    PinNo = (Int16)(x + 1),
                    SwitchState = SwitchingState.Off
                });
            }

            ExecuteLineFeed();
        }

        public static void TurnAllOn()
        {
            Output(parallelportinfo.DataPortAddress, (short)parallelportinfo.DataPortPinAssigned);
            parallelportinfo.Pins.Clear();
            for (Int16 x = 0; x < (parallelportinfo.MC74HC95_Total * 8); x++)
            {
                ExecuteStrobe();
                parallelportinfo.Pins.Add(new PinInfo
                {
                    MC74HC95_index = Convert.ToInt16(x / 8),
                    PinNo = (Int16)(x + 1),
                    SwitchState = SwitchingState.On
                });
            }

            ExecuteLineFeed();
        }

        public static void Execute()
        {
            if (parallelportinfo.Pins.Count < (parallelportinfo.MC74HC95_Total * 8))
                throw new Exception("Total number of pins are not equal to total number of MC74HC95 chips x 8");

            // turn all pins to off
            Output(parallelportinfo.DataPortAddress, (short)SwitchingState.Off);
            for (Int16 x = 0; x < (parallelportinfo.MC74HC95_Total * 8); x++)
                ExecuteStrobe();

            IList<PinInfo> debugpin = parallelportinfo.Pins.OrderByDescending(p => p.PinNo).ToList();
            foreach (PinInfo pin in debugpin)
            {
                if (pin.SwitchState == SwitchingState.On)
                {
                    Output(parallelportinfo.DataPortAddress, (short)parallelportinfo.DataPortPinAssigned);
                    ExecuteStrobe();
                }
                else
                {
                    Output(parallelportinfo.DataPortAddress, (short)SwitchingState.Off);
                    ExecuteStrobe();
                }
            }


            ExecuteLineFeed();
        }

        public static void ReadInputs()
        {
            
            IDictionary<InputPortPinAssignment, SwitchingState> ret = new Dictionary<InputPortPinAssignment, SwitchingState>();

            short status = (short)Input(parallelportinfo.StatusPortAddress);
            if (status != 0 && status != 120)
            {
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin1, SwitchingState.Off));
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin2, SwitchingState.Off));
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin3, SwitchingState.Off));
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin4, SwitchingState.Off));
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin5, SwitchingState.Off));
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin6, SwitchingState.Off));
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin7, SwitchingState.Off));
                ret.Add(new KeyValuePair<InputPortPinAssignment, SwitchingState>(InputPortPinAssignment.Pin8, SwitchingState.Off));

                switch (status)
                {
                    case 56:
                        ret[InputPortPinAssignment.Pin1] = SwitchingState.On;
                        break;
                    case 248:
                        ret[InputPortPinAssignment.Pin2] = SwitchingState.On;
                        break;
                    case 88:
                        ret[InputPortPinAssignment.Pin3] = SwitchingState.On;
                        break;
                    case 104:
                        ret[InputPortPinAssignment.Pin4] = SwitchingState.On;
                        break;
                    case 112:
                        ret[InputPortPinAssignment.Pin5] = SwitchingState.On;
                        break;
                }

                EventHandler<InputPortPinStatusArgs> statusTriggerEvent = OnInputStatusTrigger;
                if (statusTriggerEvent != null)
                {
                    statusTriggerEvent(null, new InputPortPinStatusArgs { PinStatus = ret });
                }

            }
            
        }

        private static void ExecuteStrobe()
        {
            Output(parallelportinfo.ControlPortAdress, 2);
            Output(parallelportinfo.ControlPortAdress, 3);
        }

        private static void ExecuteLineFeed()
        {
            Output(parallelportinfo.ControlPortAdress, 1);
            Output(parallelportinfo.ControlPortAdress, 3);
        }

        private static void Output(short adress, short value)
        {
            if (!isDriverOpen())
                return;
            
            if (isParallelPortInUse)
                return;

            isParallelPortInUse = true;

            if (System.Environment.Is64BitOperatingSystem)
                Output64((short)adress, (short)value);
            else
                Output32((short)adress, (short)value);

            isParallelPortInUse = false;
        }

        private static char Input(short adress)
        {
            char ret = new char();

            if (!isDriverOpen())
                return ret;

            if (isParallelPortInUse)
                return ret;

            isParallelPortInUse = true;

            if (System.Environment.Is64BitOperatingSystem)
                ret = Input64(adress);
            else
                ret = Input32(adress);

            isParallelPortInUse = false;

            return ret;
        }

        public static bool isDriverOpen()
        {
            uint nResult = 0;
            if (System.Environment.Is64BitOperatingSystem)
                nResult = IsInpOutDriverOpen_x64();
            else
                nResult = IsInpOutDriverOpen();

            return nResult == 0 ? false : true;
        }
    }

    public enum SwitchingState
    {
        On = 1,
        Off = 2
    }

    public enum DataPortPinAssignment
    {
        Pin1 = 1,
        Pin2 = 2,
        Pin3 = 4,
        Pin4 = 8,
        Pin5 = 16,
        Pin6 = 32,
        Pin7 = 64,
        Pin8 = 128,
    }

    public enum InputPortPinAssignment
    {
        //•D0: state not specified 
        //•D1: state not specified 
        //•D2: state not specified 
        //•D3: state of pin 15 (ERROR) 
        //•D4: state of pin 13 (SELECTED) 
        //•D5: state of pin 12 (PAPER OUT) 
        //•D6: state of pin 10 (ACK) 
        //•D7: state of pin 11 (BUSY) inverted 

        //Default value is 1 except Pin2(11)and Pin5(15) because its inverted

        Pin1 = 64, //Pin 10, when switched it will throw 0
        Pin2 = 128, //Pin 11, Inverted. When switched it will throw 0 instead of 128
        Pin3 = 32, //Pin 12 same as Pin 10
        Pin4 = 16, //Pin 13 same as Pin 10
        Pin5 = 8, //Pin 15
        Pin6 = 4, // NO PIN ASSIGNMENT THEREFORE NOT USED
        Pin7 = 2, // NO PIN ASSIGNMENT THEREFORE NOT USED
        Pin8 = 1 // NO PIN ASSIGNMENT THEREFORE NOT USED

        //by Default, when no one push the button/switch the value is always 8+16+32+64= 120
        //When all Pins are switched, the value is 0+0+0+0+128=128
        
        //When pin X are switched values will be:
        // pin10 = 56
        // Pin11 = 248
        // Pin12 = 88
        // Pin13 = 104
        // Pin15 = 112
    }

    public struct ParallelPortInfo
    {
        public short DataPortAddress;
        public short ControlPortAdress;
        public short StatusPortAddress;
        public short MC74HC95_Total;
        public DataPortPinAssignment DataPortPinAssigned;
        public IList<PinInfo> Pins;
    }

    public struct PinInfo
    {
        public short MC74HC95_index;
        public short PinNo;
        public SwitchingState SwitchState;
    }

    public class InputPortPinStatusArgs : EventArgs
    {
        private IDictionary<InputPortPinAssignment, SwitchingState> pinStatus;

        public IDictionary<InputPortPinAssignment, SwitchingState> PinStatus { get { return this.pinStatus; } set { this.pinStatus = value; } }        
        
    }
}



